package luadb

import (
	"database/sql"
	"errors"
	"log"
	"reflect"
	"strconv"
	"strings"
	"time"

	lua "github.com/yuin/gopher-lua"
)

// ScanSlice scan data to a slice's pointer, slice's length should equal to columns' number
func ScanSlice(rs *sql.Rows, dest interface{}) error {
	vv := reflect.ValueOf(dest)
	if vv.Kind() != reflect.Ptr || vv.Elem().Kind() != reflect.Slice {
		return errors.New("dest should be a slice's pointer")
	}

	vvv := vv.Elem()
	cols, err := rs.Columns()
	if err != nil {
		return err
	}

	newDest := make([]interface{}, len(cols))

	for j := 0; j < len(cols); j++ {
		if j >= vvv.Len() {
			newDest[j] = reflect.New(vvv.Type().Elem()).Interface()
		} else {
			newDest[j] = vvv.Index(j).Addr().Interface()
		}
	}

	err = rs.Scan(newDest...)
	if err != nil {
		return err
	}

	srcLen := vvv.Len()
	for i := srcLen; i < len(cols); i++ {
		vvv = reflect.Append(vvv, reflect.ValueOf(newDest[i]).Elem())
	}
	return nil
}

func row2table(rows *sql.Rows, tab *lua.LTable, fillColumn bool) (err error) {
	cols, err := rows.ColumnTypes()
	if err != nil {
		return
	}

	fields, err := rows.Columns()
	if err != nil {
		return
	}

	//resultsMap := make(map[string]interface{}, len(fields))
	scanResultContainers := make([]interface{}, len(fields))
	// for i := 0; i < len(fields); i++ {
	// 	var val interface{}
	// 	scanResultContainers[i] = &val //scanResultContainer
	// }

	if err := ScanSlice(rows, &scanResultContainers); err != nil {
		//if err := rows.Scan(scanResultContainers...); err != nil {
		return err
	}

	if fillColumn {
		tab.Insert(0, lua.LString(strings.Join(fields, ",")))
	}

	for i := range scanResultContainers {
		if scanResultContainers[i] == nil {
			tab.Insert(i+1, lua.LNil)
			continue
		}
		if sb, ok := scanResultContainers[i].([]byte); ok {
			if sb == nil {
				tab.Insert(i+1, lua.LNil)
				continue
			}
			s := string(sb)
			name := strings.ToUpper(cols[i].DatabaseTypeName())
			switch name {
			case "TINYINT", "SMALLINT", "MEDIUMINT", "INT", "INTEGER", "INT8", "INT16", "INT32", "UINT8", "UINT16", "UINT32", "BIGINT", "INT64", "UINT64":
				val, err := strconv.Atoi(s)
				if err != nil {
					tab.Insert(i+1, lua.LNil)
					continue
				}
				tab.Insert(i+1, lua.LNumber(val))
			case "FLOAT", "FLOAT32", "DOUBLE", "FLOAT64":
				val, err := strconv.ParseFloat(s, 64)
				if err != nil {
					tab.Insert(i+1, lua.LNil)
					continue
				}
				tab.Insert(i+1, lua.LNumber(val))
			case "DATE", "TIME", "DATETIME", "YEAR", "TIMESTAMP":
				tab.Insert(i+1, lua.LString(s))
			case "CHAR", "VARCHAR", "TINYTEXT", "TEXT", "MEDIUMTEXT", "LONGTEXT":
				tab.Insert(i+1, lua.LString(s))
			case "TINYBLOB", "BLOB", "MEDIUMBLOB", "LONGBLOB":
				tab.Insert(i+1, lua.LString(s))
			default:
				tab.Insert(i+1, lua.LString(s))
			}
		} else {
			tab.Insert(i+1, ValueOfLua(scanResultContainers[i]))
		}
		//tab.Insert(i+1, ValueOfLua(reflect.Indirect(reflect.ValueOf(scanResultContainers[i])).Interface()))
		//tab.RawSetString(fields[i], ValueOfLua(reflect.Indirect(reflect.ValueOf(scanResultContainers[i])).Interface()))
	}
	return nil
}

//ValueOfLua 值转换
func ValueOfLua(arg interface{}) lua.LValue {
	switch f := arg.(type) {
	case bool:
		return lua.LBool(f)
	case float32:
		return lua.LNumber(f)
	case float64:
		return lua.LNumber(f)
	case int:
		return lua.LNumber(f)
	case int8:
		return lua.LNumber(f)
	case int16:
		return lua.LNumber(f)
	case int32:
		return lua.LNumber(f)
	case int64:
		return lua.LNumber(f)
	case uint:
		return lua.LNumber(f)
	case uint8:
		return lua.LNumber(f)
	case uint16:
		return lua.LNumber(f)
	case uint32:
		return lua.LNumber(f)
	case uint64:
		return lua.LNumber(f)
	case uintptr:
		return lua.LNumber(f)
	case string:
		return lua.LString(f)
	case []byte:
		return lua.LString(f)
	case time.Time:
		return lua.LNumber(f.Unix())
	}
	log.Println(reflect.TypeOf(arg).Name(), arg)
	return lua.LNil
}
